using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.Xml;
using System.IO;
using System.Data.SqlClient;
using MySql.Data;
using MySql.Data.MySqlClient;
using System.Data.OleDb;
using System.Data.OracleClient;
//using MultiClassDBA.ChannelBuilder;
using Npgsql;

namespace GuavaData
{
    public class OuterUnionException : Exception
    {
        public OuterUnionException() : base() { }
        public OuterUnionException(string s) : base(s) { }
        public OuterUnionException(string s, Exception innerException) : base(s, innerException) { }
    }

    public class Schema : Object
    {
        public List<Scheme> tables;

        public Schema()
        {
            this.tables = new List<Scheme>();
        }

        public static Schema SchemaFromDB()
        {
            DataTable tableColumnInfo = DBConnection.ExecuteQuery("select * from information_schema.columns order by table_name").Tables[0];
            DataTable keyInfo = DBConnection.ExecuteQuery("select * from information_schema.key_column_usage order by table_name").Tables[0];
            DataTable foreignKeyInfo = DBConnection.ExecuteQuery("select * from INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS").Tables[0];
            Dictionary<string, List<Column>> tableColumnMap = new Dictionary<string, List<Column>>();
            Dictionary<string, List<Column>> tablePrimaryKeyMap = new Dictionary<string, List<Column>>();
            Dictionary<string, List<Column>> tableForeignKeyMap = new Dictionary<string, List<Column>>();
            Dictionary<string, string> parent = new Dictionary<string, string>();
            Dictionary<string, List<string>> children = new Dictionary<string, List<string>>();
            Schema sch = new Schema();
            for (int i = 0; i < tableColumnInfo.Rows.Count; i++)
            {
                string tableName = tableColumnInfo.Rows[i]["Table_Name"].ToString();
                Column col = new Column(tableColumnInfo.Rows[i]["Column_Name"].ToString(), Domain.Domain.FromDBInfoRow(tableColumnInfo.Rows[i]));
                if (tableColumnMap.ContainsKey(tableName))
                    tableColumnMap[tableName].Add(col);
                else
                {
                    List<Column> cols = new List<Column>();
                    cols.Add(col);
                    tableColumnMap.Add(tableName, cols);
                }
                var keyRows = keyInfo.Select("Table_Name='" + tableName + "' and Column_Name='" + tableColumnInfo.Rows[i]["Column_Name"].ToString() + "'");
                if (keyRows.Length > 0)
                {
                    var fkRows = foreignKeyInfo.Select("Constraint_Name='" + keyRows[0]["Constraint_Name"] + "'");
                    if (fkRows.Length > 0)
                    {
                        var parentKeyRow = keyInfo.Select("CONSTRAINT_NAME='" + fkRows[0]["UNIQUE_CONSTRAINT_NAME"] + "'");
                        string parentTable = parentKeyRow[0]["Table_Name"].ToString();
                        if (!parent.ContainsKey(tableName))
                        {
                            parent.Add(tableName, parentTable);
                        }
                        if (children.ContainsKey(parentTable))
                            children[parentTable].Add(tableName);
                        else
                        {
                            List<string> cList = new List<string>();
                            cList.Add(tableName);
                            children.Add(parentTable, cList);
                        }
                    }
                    else
                    {
                        if (tablePrimaryKeyMap.ContainsKey(tableName))
                            tablePrimaryKeyMap[tableName].Add(col);
                        else
                        {
                            List<Column> cols = new List<Column>();
                            cols.Add(col);
                            tablePrimaryKeyMap.Add(tableName, cols);
                        }

                    }
                }
            }
            Dictionary<string, Scheme> tableSchemeMap = new Dictionary<string, Scheme>();
            foreach (String key in tableColumnMap.Keys)
            {
                Scheme scheme = sch.newScheme(key, null);
                if (children.ContainsKey(key))
                    scheme.children = children[key];
                scheme.columns = tableColumnMap[key];
                if (tablePrimaryKeyMap.ContainsKey(key))
                    scheme.pkColumns = tablePrimaryKeyMap[key];
                if (tableForeignKeyMap.ContainsKey(key))
                    scheme.fkColumns = tableForeignKeyMap[key];

                tableSchemeMap.Add(key, scheme);
            }
            foreach (String key in parent.Keys)
                tableSchemeMap[key].parents.Add(tableSchemeMap[parent[key]]);
            return sch;
        }

        public Scheme getScheme(String name)
        {
            //Searches through the list of schemes that are part of this schema
            //and returns the first one it finds with a name matching the argument "name"
            foreach (Scheme s in tables)
                if (s.TableName == name)
                    return s;
            return null;//throw new Exception("Table " + name + " not found.");
        }

        public static Schema SchemaFromGTree(String path)
        {
            //loads a schema in the form of an XML GTree from the location "path"

            Schema s = new Schema();
            XmlDocument xDoc = new XmlDocument();
            try
            {
                xDoc.Load(path);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            RecursiveSchemaFromGTree(s, xDoc);

            return s;
        }

        private static void RecursiveSchemaFromGTree(Schema sch, XmlNode current)
        {
            //Recursively scan a GTree, picking out the nodes that correspond to tables and adding them
            //to the schema "sch". Also, add GTree elements that represent columns, detect the
            //foreign key relations from the GTree's structure, and add them to the schema as well.

            //Make sure the current node has the information we need
            if ((current.Attributes != null) && (current.Attributes.GetNamedItem("ControlType") != null))
            {
                //if this is a node that should represent a table, add it to the schema
                if (current.Attributes.GetNamedItem("ControlType").InnerText == "Entity")
                {



                    String tname = current.Name;
                    tname = tname.Replace("(", "");
                    tname = tname.Replace(")", "");
                    Scheme newScheme = null;
                    bool found = false;
                    foreach (Scheme s1 in sch.tables)
                        if (s1.TableName == tname)
                        {
                            newScheme = s1;
                            found = true;
                            break;
                        }

                    if (!found)
                    {
                        newScheme = sch.newScheme(tname, null);
                        newScheme.columns = new List<Column>();
                    }




                    //Determine if this table is a single launch
                    bool singleLaunch = false;
                    if (current.Attributes.GetNamedItem("launchType") != null
                        && current.Attributes.GetNamedItem("launchType").InnerText == "Single")
                        singleLaunch = true;

                    //Find FKs for this Table
                    XmlNode temp = current;
                    while (temp.ParentNode != null)
                    {
                        temp = temp.ParentNode;
                        if ((temp.Attributes != null) && (temp.Attributes.GetNamedItem("ControlType") != null) && temp.Attributes.GetNamedItem("ControlType").InnerText == "Entity")
                        {
                            String t2name = temp.Name;
                            t2name = t2name.Replace("(", "");
                            t2name = t2name.Replace(")", "");
                            Scheme fkTable = sch.getScheme(t2name);

                            if (fkTable != null && !newScheme.parents.Contains(fkTable))
                                newScheme.parents.Add(fkTable);
                            if (singleLaunch)
                                newScheme.newColumn("id", new GuavaData.Domain.UniqueIdentifier(), true, true);
                            else
                            {
                                newScheme.newColumn("id", new GuavaData.Domain.UniqueIdentifier(), true, false);
                                newScheme.newColumn("fk", new GuavaData.Domain.UniqueIdentifier(), false, true);
                            }

                            fkTable.children.Add(newScheme.TableName);
                            break;


                        }
                        if (temp.ParentNode == null)
                        {
                            newScheme.newColumn("id", new GuavaData.Domain.UniqueIdentifier(), true, false);
                            newScheme.newColumn("fk", new GuavaData.Domain.UniqueIdentifier(), false, true);
                        }
                    }

                }

                //if we find a node that represents an attribute, add that attribute to its parent table    
                if (current.Attributes.GetNamedItem("ControlType").InnerText == "Attribute")
                {
                    XmlNode temp = current;
                    while (temp.ParentNode != null)
                    {
                        temp = temp.ParentNode;
                        if ((temp.Attributes != null) && (temp.Attributes.GetNamedItem("ControlType") != null) && temp.Attributes.GetNamedItem("ControlType").InnerText == "Entity")
                        {
                            String tname = temp.Name;
                            tname = tname.Replace("(", "");
                            tname = tname.Replace(")", "");
                            Scheme parentTable = sch.getScheme(tname);

                            //To do: detect the domain of the column
                            Domain.Domain d = null;
                            String gTreeType = "";
                            if (current.Attributes.GetNamedItem("Type") != null)
                            {
                                gTreeType = current.Attributes.GetNamedItem("Type").InnerText;
                                //System.Console.WriteLine(gTreeType);
                            }
                            if (gTreeType == "uniqueidentifier")
                                d = new Domain.UniqueIdentifier();
                            else if (gTreeType == "datetime" || gTreeType == "date" || gTreeType == "time")
                                d = new Domain.DateTime();
                            else if (gTreeType == "text" || gTreeType.Contains("nvar") || gTreeType.Contains("nchar"))
                                d = new Domain.String();
                            else if (gTreeType == "tinyint")
                                d = new Domain.TinyInteger();
                            else if (gTreeType == "smallint")
                                d = new Domain.SmallInteger();
                            else if (gTreeType == "int")
                                d = new Domain.Integer();
                            else if (gTreeType == "bigint")
                                d = new Domain.BigInteger();
                            else if (gTreeType == "float" || gTreeType == "real")
                                d = new Domain.Double();
                            else if (gTreeType == "bit")
                                d = new Domain.Boolean();
                            else if (gTreeType.Contains("varchar"))
                                d = new Domain.String(Convert.ToInt16(textBetween(gTreeType, "(", ")", 0)));
                            else
                                throw new Exception("Data type " + gTreeType + " not recognized.");
                            Column c = parentTable.newColumn(current.Name, d, false, false);

                            foreach (XmlNode x in current.ChildNodes)
                                if (x.Name == "Domain" && x.Attributes.GetNamedItem("Type").InnerText == "Enumerated")
                                {
                                    Domain.Enumerated en = new Domain.Enumerated();
                                    foreach (XmlNode n in x.ChildNodes)
                                    {
                                        if (n != null)
                                        {
                                            en.Items.Add(n.InnerText);
                                        }
                                    }
                                    c.dom = en;
                                }

                            break;
                        }
                    }

                }
            }

            //recurse!
            foreach (XmlNode x in current.ChildNodes)
            {
                RecursiveSchemaFromGTree(sch, x);
            }
        }

        public static Schema SchemaFromCreateTableScript(String s)
        {
            //Unreliable. Parse the Create Table/Alter Table/Creat Trigger script and 
            //create a Schema from the input.

            System.Console.WriteLine(s);
            Schema sch = new Schema();
            int position = 0;
            while (true)
            {
                int CTindex = s.IndexOf("CREATE TABLE", position);
                int ATindex = s.IndexOf("ALTER TABLE", position);
                int triggerIndex = s.IndexOf("CREATE Trigger", position);
                int min = -1;

                if (CTindex >= 0)
                    min = CTindex;
                if ((ATindex >= 0 && min == -1) || (ATindex >= 0 && ATindex < min))
                    min = ATindex;
                if ((triggerIndex >= 0 && min == -1) || (triggerIndex >= 0 && triggerIndex < min))
                    min = triggerIndex;
                if (min == -1)
                    break;

                if (CTindex == min)
                {
                    position = CTindex + 13;
                    int end = s.IndexOf("(", position) - 1;
                    String tableName = s.Substring(position, end - position);
                    Scheme currentScheme = sch.newScheme(tableName, null);
                    //System.Console.WriteLine("Table: " + tableName);
                    position = end + 2;
                    while (true)
                    {
                        end = s.IndexOf(",", position);
                        String columnText = s.Substring(position, end - position);
                        if (columnText.Contains("CONSTRAINT"))
                        {
                            int b = columnText.IndexOf("(") + 1;
                            end = columnText.IndexOf(")", b);
                            if (end == -1)//compound PK
                            {
                                end = s.IndexOf(")", position + b);
                                String pkString = s.Substring(position + b, end - (position + b));
                                foreach (String str in pkString.Split(",".ToCharArray()))
                                    currentScheme.pkColumns.Add(new Column(str, new Domain.UniqueIdentifier()));
                            }
                            else//regular PK
                            {
                                String pkColumnName = columnText.Substring(b, end - b);
                                currentScheme.pkColumns.Add(new Column(pkColumnName, new Domain.UniqueIdentifier()));
                            }
                            position = s.IndexOf("\r\n", position);

                            break;
                        }
                        position = end;
                        columnText = columnText.TrimStart(new char[] { "(".ToCharArray()[0] });
                        columnText = columnText.Trim();
                        end = columnText.IndexOf(" ");
                        String columnName = columnText.Substring(0, end);
                        int a = columnText.IndexOf(" ", end + 1);
                        String dataType = columnText.Substring(end + 1, a - end);
                        dataType = dataType.Trim();
                        //System.Console.WriteLine(dataType);
                        Domain.Domain d = null;
                        if (dataType == "uniqueidentifier")
                            d = new Domain.UniqueIdentifier();
                        else if (dataType == "datetime")
                            d = new Domain.DateTime();
                        else if (dataType == "text")
                            d = new Domain.String();
                        else if (dataType == "tinyint")
                            d = new Domain.TinyInteger();
                        else if (dataType == "smallint")
                            d = new Domain.SmallInteger();
                        else if (dataType == "int")
                            d = new Domain.Integer();
                        else if (dataType == "bit")
                            d = new Domain.Boolean();
                        else if (dataType.Contains("varchar"))
                            d = new Domain.String(Convert.ToInt16(textBetween(dataType, "(", ")", 0)));
                        else
                            throw new Exception("Data type " + dataType + " not recognized.");
                        currentScheme.columns.Add(new Column(columnName, d));
                        position++;

                    }
                }
                else if (ATindex == min)
                {
                    position = ATindex;
                    String temp = s.Substring(position, s.IndexOf("\r\n", position) - position);
                    if (temp.Contains("ADD CONSTRAINT"))
                    {
                        String tableName = textBetween(s, "ALTER TABLE", "ADD CONSTRAINT", position);
                        String fkColumnName = textBetween(s, "FOREIGN KEY (", ") REFERENCES", position);
                        String toTableName = textBetween(s, "REFERENCES", "(", position);
                        //System.Console.WriteLine(tableName + "," + fkColumnName + "," + toTableName);
                        Scheme t = sch.getScheme(tableName);

                        if (t.pkString == fkColumnName && t.pkColumns.Count == 1)//This will have to be modified for compound keys
                        {
                            t.newColumn("id", new GuavaData.Domain.UniqueIdentifier(), true, true);
                        }
                        else
                            t.newColumn(fkColumnName, new Domain.UniqueIdentifier(), false, true);
                        t.parents.Add(sch.getScheme(toTableName));
                        foreach (Scheme s1 in t.parents)
                            s1.children.Add(t.tableName);
                        position = s.IndexOf("\r\n", position);
                    }
                    else
                        position = s.IndexOf("\r\n", position);
                }
                else if (triggerIndex == min)
                {
                    position = triggerIndex + 15;
                    int a = s.IndexOf(" UNION ", position);
                    int b = a;
                    while (!s.Substring(b, a - b).Contains("FROM"))
                    {
                        b -= 2;
                    }
                    String t1name = s.Substring(b, a - b);
                    t1name = t1name.Replace("FROM", "");
                    t1name = t1name.Trim();

                    String t2name = textBetween(s, "FROM", ")", a);
                    String tableName = textBetween(s, " ON ", " INSTEAD OF INSERT ", position);


                    System.Console.WriteLine("Teir 2 FK: " + tableName + " -> " + t1name + " or " + t2name);


                    Scheme schemeA = sch.getScheme(tableName);

                    schemeA.parents.Add(sch.getScheme(t1name));
                    schemeA.parents.Add(sch.getScheme(t2name));
                    foreach (Scheme s1 in schemeA.parents)
                        s1.children.Add(schemeA.TableName);


                    schemeA.newColumn("id", new GuavaData.Domain.UniqueIdentifier(), true, false);
                    schemeA.newColumn("fk", new GuavaData.Domain.UniqueIdentifier(), false, true);
                }
                else
                    break;
            }

            return sch;
        }

        public static Schema SchemaFromGuavaTransaction(Transaction t)
        {
            //Create a Schema object from the AddTable/ForeignKey commands in "t"

            Schema sch = new Schema();
            List<AddTable> tables = new List<AddTable>();
            List<AddColumn> columns = new List<AddColumn>();
            List<ForeignKey> FKs = new List<ForeignKey>();

            foreach (Command c in t.Contents)
            {
                Type type = c.root.GetType();
                switch (type.ToString())
                {
                    case "GuavaData.AddTable":
                        tables.Add((AddTable)c.root);
                        break;
                    case "GuavaData.ForeignKey":
                        FKs.Add((ForeignKey)c.root);
                        break;
                    case "GuavaData.AddColumn":
                        columns.Add((AddColumn)c.root);
                        break;
                    case "GuavaData.AddElement":
                        System.Console.WriteLine("Add Element");
                        System.Console.WriteLine("  " + ((AddElement)c.root).Table);
                        System.Console.WriteLine("  " + ((AddElement)c.root).Column);
                        System.Console.WriteLine("  " + ((AddElement)c.root).Element);
                        break;
                    default:
                        System.Console.WriteLine("Unrecognized command type: " + type.ToString());
                        break;
                }
            }

            foreach (AddTable at in tables)
            {
                Scheme ns = sch.newScheme(at.Table, null);
                for (int i = 0; i < at.Columns.Count; i++)
                    ns.newColumn(at.Columns[i], at.Domains[i], at.Keys.Contains(at.Columns[i]), false);
            }
            foreach (AddColumn ac in columns)
            {
                Scheme temp = sch.getScheme(ac.Table);
                temp.newColumn(ac.Column, ac.Domain, false, false);
            }
            foreach (ForeignKey fk in FKs)
            {
                Scheme child = sch.getScheme(fk.ChildTable);
                Scheme parent = sch.getScheme(fk.ParentTable);
                foreach (String s in fk.ChildColumns)
                    if (!child.fkColumns.Contains(child.getColumn(s)))
                        child.fkColumns.Add(child.getColumn(s));
                parent.children.Add(child.TableName);
                child.parents.Add(parent);
            }

            return sch;
        }

        public static Schema QuerySchemaFromDB(SqlConnection conn)
        {

            DataTable columnsResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.Columns);//Columns - Gets Columns by table
            DataTable tablesResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.Views);//view name in index 2
            DataTable primaryKeysResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.IndexColumns);

            List<String> views = new List<String>();
            foreach (DataRow dr in tablesResult.Rows)
                views.Add(dr.ItemArray[2].ToString());

            //Query adapted from: http://blog.sqlauthority.com/2006/11/01/sql-server-query-to-display-foreign-key-relationships-and-name-of-the-constraint-for-each-table-in-database/
            String q = "SELECT" + "\r\n" +
                "FK_Table = FK.TABLE_NAME," + "\r\n" +
                "FK_Column = CU.COLUMN_NAME," + "\r\n" +
                "PK_Table = PK.TABLE_NAME," + "\r\n" +
                "PK_Column = PT.COLUMN_NAME" + "\r\n" +
                "FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME" + "\r\n" +
                "INNER JOIN (" + "\r\n" +
                "SELECT i1.TABLE_NAME, i2.COLUMN_NAME" + "\r\n" +
                "FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME" + "\r\n" +
                "WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY'" + "\r\n" +
                ") PT ON PT.TABLE_NAME = PK.TABLE_NAME";
            System.Data.SqlClient.SqlDataAdapter sda = new System.Data.SqlClient.SqlDataAdapter(q, conn);
            DataSet ds = new DataSet();
            sda.Fill(ds);
            DataTable foreignKeysResult = ds.Tables[0];

            sda = new System.Data.SqlClient.SqlDataAdapter("SELECT name, text FROM sys.triggers JOIN syscomments ON sys.triggers.object_id = syscomments.id", conn);
            ds = new DataSet();
            sda.Fill(ds);
            DataTable triggersResult = ds.Tables[0];


            Schema s = new Schema();

            foreach (DataColumn dc in columnsResult.Columns)
                System.Console.Write(dc.ColumnName + ", ");
            System.Console.WriteLine();
            foreach (DataRow dr in columnsResult.Rows)
            {
                if (!views.Contains((String)dr.ItemArray[2]))
                {
                    Scheme current = s.getScheme((String)dr.ItemArray[2]);
                    if (current == null)
                        current = s.newScheme((String)dr.ItemArray[2], null);
                    GuavaData.Domain.Domain d = null;
                    if ((String)dr.ItemArray[7] == "uniqueidentifier")
                        d = new Domain.UniqueIdentifier();
                    else if ((String)dr.ItemArray[7] == "datetime")
                        d = new Domain.DateTime();
                    else if ((String)dr.ItemArray[7] == "text")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "tinyint")
                        d = new Domain.TinyInteger();
                    else if ((String)dr.ItemArray[7] == "smallint")
                        d = new Domain.SmallInteger();
                    else if ((String)dr.ItemArray[7] == "int")
                        d = new Domain.Integer();
                    else if ((String)dr.ItemArray[7] == "bit")
                        d = new Domain.Boolean();
                    else if (((String)dr.ItemArray[7]).Contains("varchar"))
                        d = new Domain.String();//normally we'd specify a length, but I'm not sure which ItemArray index specifies this and it doesnt really matter to us.
                    else if ((String)dr.ItemArray[7] == "char")
                        d = new Domain.TinyInteger();
                    else if ((String)dr.ItemArray[7] == "bigint")
                        d = new Domain.BigInteger();
                    else if ((String)dr.ItemArray[7] == "numeric")
                        d = new Domain.Integer();
                    else if ((String)dr.ItemArray[7] == "float")
                        d = new Domain.Single();
                    else if ((String)dr.ItemArray[7] == "varbinary")
                        d = new Domain.BigInteger();
                    else if ((String)dr.ItemArray[7] == "image")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "smalldatetime")
                        d = new Domain.DateTime();
                    else if ((String)dr.ItemArray[7] == "nchar")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "timestamp" || (String)dr.ItemArray[7] == "date")
                        d = new Domain.DateTime();
                    else
                        throw new Exception("Data type " + (String)dr.ItemArray[7] + " not recognized.");
                    current.newColumn((String)dr.ItemArray[3], d, false, false);
                }

            }

            foreach (DataRow dr in foreignKeysResult.Rows)
            {
                Scheme child = s.getScheme(((String)dr.ItemArray[0]));
                Scheme parent = s.getScheme(((String)dr.ItemArray[2]));
                if (child == null || parent == null)
                    throw new Exception("Error processing foreign keys");
                child.parents.Add(parent);
                parent.children.Add(child.tableName);
                Column newCol = child.getColumn((String)dr.ItemArray[1]);
                if (!child.fkColumns.Contains(newCol))
                    child.fkColumns.Add(newCol);
                newCol = parent.getColumn((String)dr.ItemArray[3]);
                if (!parent.pkColumns.Contains(newCol))
                    parent.pkColumns.Add(newCol);


            }

            foreach (DataRow dr in triggersResult.Rows)
            {
                List<String> mask = new List<String>();
                mask.Add("CREATE Trigger ");
                mask.Add("ON");
                mask.Add("INSTEAD OF INSERT AS DECLARE @count int SELECT @count=count(*) FROM INSERTED I@ WHERE NOT EXISTS (SELECT UNIONED@SELECTS");
                mask.Add(" FROM (SELECT");
                mask.Add("as");
                mask.Add("FROM");
                mask.Add("UNION SELECT");
                mask.Add("as");
                mask.Add("FROM");
                mask.Add(") AS UNIONED@SELECTS WHERE I@.");
                mask.Add("=UNIONED@SELECTS.");
                mask.Add(")");
                mask.Add("IF(@count=0)");
                mask.Add("insertinto");
                mask.Add(" select * from INSERTED");
                mask.Add("ELSE raiserror('Foreign key violation',18,1)");

                MaskParser mp = new MaskParser(mask);
                if (mp.fitsMask((String)dr.ItemArray[1]))
                {
                    List<String> result = mp.parse((String)dr.ItemArray[1]);
                    //System.Console.WriteLine(result[4]);//pk for table 1[5]
                    //System.Console.WriteLine(result[5]);//table 1
                    //System.Console.WriteLine(result[7]);//pk for table 2[8]
                    //System.Console.WriteLine(result[8]);//table 2
                    //System.Console.WriteLine(result[13]);//FK table

                    Scheme t1 = s.getScheme(result[5]);
                    Scheme t2 = s.getScheme(result[8]);
                    Scheme ft = s.getScheme(result[13]);
                    ft.parents.Add(t1);
                    ft.parents.Add(t2);
                    t1.children.Add(ft.tableName);
                    t2.children.Add(ft.tableName);
                }
                else
                    System.Console.WriteLine("Unrecognized Trigger: " + (String)dr.ItemArray[1]);

            }

            foreach (DataRow dr in primaryKeysResult.Rows)
            {
                Scheme sch = s.getScheme((String)dr.ItemArray[5]);
                if (sch == null)
                    throw new Exception("Error processing primary keys");
                if (!sch.pkColumns.Contains(sch.getColumn((String)dr.ItemArray[6])))
                    sch.pkColumns.Add(sch.getColumn((String)dr.ItemArray[6]));
            }
            return s;
        }

        public static Schema QuerySchemaFromDB(MySqlConnection conn)
        {
            DataTable columnsResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.Columns);//Columns - Gets Columns by table
            DataTable tablesResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.Views);//view name in index 2
            DataTable primaryKeysResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.IndexColumns);


            List<String> views = new List<String>();
            foreach (DataRow dr in tablesResult.Rows)
                views.Add(dr.ItemArray[2].ToString());

            //Query adapted from: http://blog.sqlauthority.com/2006/11/01/sql-server-query-to-display-foreign-key-relationships-and-name-of-the-constraint-for-each-table-in-database/
            String q = "SELECT" + "\r\n" +
                "FK.TABLE_NAME as FK_Table," + "\r\n" +
                "CU.COLUMN_NAME as FK_Column," + "\r\n" +
                "PK.TABLE_NAME as PK_Table," + "\r\n" +
                "PT.COLUMN_NAME as PK_Column" + "\r\n" +
                "FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME" + "\r\n" +
                "INNER JOIN (" + "\r\n" +
                "SELECT i1.TABLE_NAME, i2.COLUMN_NAME" + "\r\n" +
                "FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME" + "\r\n" +
                "WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY'" + "\r\n" +
                ") PT ON PT.TABLE_NAME = PK.TABLE_NAME";
            MySqlDataAdapter sda = new MySqlDataAdapter(q, conn);
            DataSet ds = new DataSet();
            sda.Fill(ds);
            DataTable foreignKeysResult = ds.Tables[0];

            //sda = new MySqlDataAdapter("SELECT name, text FROM sys.triggers JOIN syscomments ON sys.triggers.object_id = syscomments.id", conn);
            //ds = new DataSet();
            //sda.Fill(ds);
            //DataTable triggersResult = ds.Tables[0];


            Schema s = new Schema();

            foreach (DataColumn dc in columnsResult.Columns)
                System.Console.Write(dc.ColumnName + ", ");
            System.Console.WriteLine();
            foreach (DataRow dr in columnsResult.Rows)
            {
                if (!views.Contains((String)dr.ItemArray[2]))
                {
                    Scheme current = s.getScheme((String)dr.ItemArray[2]);
                    if (current == null)
                        current = s.newScheme((String)dr.ItemArray[2], null);
                    GuavaData.Domain.Domain d = null;
                    if ((String)dr.ItemArray[7] == "uniqueidentifier")
                        d = new Domain.UniqueIdentifier();
                    else if ((String)dr.ItemArray[7] == "datetime")
                        d = new Domain.DateTime();
                    else if ((String)dr.ItemArray[7] == "text")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "mediumtext")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "tinytext")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "longtext")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "tinyint")
                        d = new Domain.TinyInteger();
                    else if ((String)dr.ItemArray[7] == "smallint")
                        d = new Domain.SmallInteger();
                    else if ((String)dr.ItemArray[7] == "mediumint")
                        d = new Domain.Integer();
                    else if ((String)dr.ItemArray[7] == "int")
                        d = new Domain.Integer();
                    else if ((String)dr.ItemArray[7] == "bit")
                        d = new Domain.Boolean();
                    else if (((String)dr.ItemArray[7]).Contains("varchar"))
                        d = new Domain.String();//normally we'd specify a length, but I'm not sure which ItemArray index specifies this and it doesnt really matter to us.
                    else if ((String)dr.ItemArray[7] == "char")
                        d = new Domain.TinyInteger();
                    else if ((String)dr.ItemArray[7] == "bigint")
                        d = new Domain.BigInteger();
                    else if ((String)dr.ItemArray[7] == "numeric")
                        d = new Domain.Integer();
                    else if ((String)dr.ItemArray[7] == "float")
                        d = new Domain.Single();
                    else if ((String)dr.ItemArray[7] == "double")
                        d = new Domain.Double();
                    else if ((String)dr.ItemArray[7] == "varbinary")
                        d = new Domain.BigInteger();
                    else if ((String)dr.ItemArray[7] == "image")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "smalldatetime")
                        d = new Domain.DateTime();
                    else if ((String)dr.ItemArray[7] == "nchar")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "blob")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "longblob")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "tinyblob")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "mediumblob")
                        d = new Domain.String();
                    else if ((String)dr.ItemArray[7] == "timestamp" || (String)dr.ItemArray[7] == "date")
                        d = new Domain.DateTime();
                    else if ((String)dr.ItemArray[7] == "decimal")
                        d = new Domain.Decimal();
                    else
                        throw new Exception("Data type " + (String)dr.ItemArray[7] + " not recognized.");
                    current.newColumn((String)dr.ItemArray[3], d, false, false);
                }

            }

            foreach (DataRow dr in foreignKeysResult.Rows)
            {
                Scheme child = s.getScheme(((String)dr.ItemArray[0]));
                Scheme parent = s.getScheme(((String)dr.ItemArray[2]));
                if (child == null || parent == null)
                    throw new Exception("Error processing foreign keys");
                child.parents.Add(parent);
                parent.children.Add(child.tableName);
                Column newCol = child.getColumn((String)dr.ItemArray[1]);
                if (!child.fkColumns.Contains(newCol))
                    child.fkColumns.Add(newCol);
                newCol = parent.getColumn((String)dr.ItemArray[3]);
                if (!parent.pkColumns.Contains(newCol))
                    parent.pkColumns.Add(newCol);


            }
            /*
            foreach (DataRow dr in triggersResult.Rows)
            {
                List<String> mask = new List<String>();
                mask.Add("CREATE Trigger ");
                mask.Add("ON");
                mask.Add("INSTEAD OF INSERT AS DECLARE @count int SELECT @count=count(*) FROM INSERTED I@ WHERE NOT EXISTS (SELECT UNIONED@SELECTS");
                mask.Add(" FROM (SELECT");
                mask.Add("as");
                mask.Add("FROM");
                mask.Add("UNION SELECT");
                mask.Add("as");
                mask.Add("FROM");
                mask.Add(") AS UNIONED@SELECTS WHERE I@.");
                mask.Add("=UNIONED@SELECTS.");
                mask.Add(")");
                mask.Add("IF(@count=0)");
                mask.Add("insertinto");
                mask.Add(" select * from INSERTED");
                mask.Add("ELSE raiserror('Foreign key violation',18,1)");

                MaskParser mp = new MaskParser(mask);
                if (mp.fitsMask((String)dr.ItemArray[1]))
                {
                    List<String> result = mp.parse((String)dr.ItemArray[1]);
                    //System.Console.WriteLine(result[4]);//pk for table 1[5]
                    //System.Console.WriteLine(result[5]);//table 1
                    //System.Console.WriteLine(result[7]);//pk for table 2[8]
                    //System.Console.WriteLine(result[8]);//table 2
                    //System.Console.WriteLine(result[13]);//FK table

                    Scheme t1 = s.getScheme(result[5]);
                    Scheme t2 = s.getScheme(result[8]);
                    Scheme ft = s.getScheme(result[13]);
                    ft.parents.Add(t1);
                    ft.parents.Add(t2);
                    t1.children.Add(ft.tableName);
                    t2.children.Add(ft.tableName);
                }
                else
                    System.Console.WriteLine("Unrecognized Trigger: " + (String)dr.ItemArray[1]);

            }
            */
            foreach (DataRow dr in primaryKeysResult.Rows)
            {
                Scheme sch = s.getScheme((String)dr.ItemArray[3]);
                if (sch == null)
                    throw new Exception("Error processing primary keys");
                if (!sch.pkColumns.Contains(sch.getColumn((String)dr.ItemArray[4])))
                    sch.pkColumns.Add(sch.getColumn((String)dr.ItemArray[4]));
            }
            return s;
        }

        public static Schema QuerySchemaFromDB(OleDbConnection conn)
        {
            DataTable columnsResult = conn.GetSchema(OleDbMetaDataCollectionNames.Columns);//Columns - Gets Columns by table
            DataTable tablesResult = conn.GetSchema(OleDbMetaDataCollectionNames.Views);//view name in index 2
            DataTable primaryKeysResult = conn.GetSchema(OleDbMetaDataCollectionNames.Indexes);


            List<String> views = new List<String>();
            foreach (DataRow dr in tablesResult.Rows)
                views.Add(dr.ItemArray[2].ToString());


            String q = "SELECT \r\n" +
                "szObject AS FK_Table, \r\n" +
                "szColumn AS FK_Column, \r\n" +
                "szReferencedObject AS PK_Table, \r\n" +
                "szReferencedColumn AS PK_Column \r\n" +
                "FROM MSysRelationships \r\n" +
                "WHERE szObject NOT LIKE 'MSysNavPane%'";
            OleDbDataAdapter sda = new OleDbDataAdapter(q, conn);
            DataSet ds = new DataSet();
            sda.Fill(ds);
            DataTable foreignKeysResult = ds.Tables[0];


            //sda = new MySqlDataAdapter("SELECT name, text FROM sys.triggers JOIN syscomments ON sys.triggers.object_id = syscomments.id", conn);
            //ds = new DataSet();
            //sda.Fill(ds);
            //DataTable triggersResult = ds.Tables[0];


            Schema s = new Schema();

            foreach (DataColumn dc in columnsResult.Columns)
                System.Console.Write(dc.ColumnName + ", ");
            System.Console.WriteLine();

            foreach (DataRow dr in columnsResult.Rows)
            {
                if (!((String)dr.ItemArray[2]).StartsWith("MSys") &&         // ignore system table
                    !views.Contains((String)dr.ItemArray[2]))
                {
                    Scheme current = s.getScheme((String)dr.ItemArray[2]);
                    if (current == null)
                        current = s.newScheme((String)dr.ItemArray[2], null);
                    GuavaData.Domain.Domain d = null;
                    if ((int)dr.ItemArray[11] == 7)     // Datetime
                        d = new Domain.DateTime();
                    else if ((int)dr.ItemArray[11] == 130)      // hyperlink, memo, text
                        d = new Domain.String();
                    else if ((int)dr.ItemArray[11] == 3)    // autoincrement, number
                        d = new Domain.Integer();
                    else if ((int)dr.ItemArray[11] == 11)      // yes/no (boolean)
                        d = new Domain.Boolean();
                    else if ((int)dr.ItemArray[11] == 6)        // currency
                        d = new Domain.Single();
                    else if ((int)dr.ItemArray[11] == 128)      // OLEObject
                        d = new Domain.String();
                    else
                        d = new Domain.String();
                    //throw new Exception("Data type " + (int)dr.ItemArray[11] + " not recognized.");
                    current.newColumn((String)dr.ItemArray[3], d, false, false);
                }

            }

            foreach (DataRow dr in foreignKeysResult.Rows)
            {
                Scheme child = s.getScheme(((String)dr.ItemArray[0]));
                Scheme parent = s.getScheme(((String)dr.ItemArray[2]));
                if (child == null || parent == null)
                    throw new Exception("Error processing foreign keys");
                child.parents.Add(parent);
                parent.children.Add(child.tableName);
                Column newCol = child.getColumn((String)dr.ItemArray[1]);
                if (!child.fkColumns.Contains(newCol))
                    child.fkColumns.Add(newCol);
                newCol = parent.getColumn((String)dr.ItemArray[3]);
                if (!parent.pkColumns.Contains(newCol))
                    parent.pkColumns.Add(newCol);
                if (s.tables.IndexOf(child) < s.tables.IndexOf(parent))
                    Swap(s, child, parent);
            }
            /*
            foreach (DataRow dr in triggersResult.Rows)
            {
                List<String> mask = new List<String>();
                mask.Add("CREATE Trigger ");
                mask.Add("ON");
                mask.Add("INSTEAD OF INSERT AS DECLARE @count int SELECT @count=count(*) FROM INSERTED I@ WHERE NOT EXISTS (SELECT UNIONED@SELECTS");
                mask.Add(" FROM (SELECT");
                mask.Add("as");
                mask.Add("FROM");
                mask.Add("UNION SELECT");
                mask.Add("as");
                mask.Add("FROM");
                mask.Add(") AS UNIONED@SELECTS WHERE I@.");
                mask.Add("=UNIONED@SELECTS.");
                mask.Add(")");
                mask.Add("IF(@count=0)");
                mask.Add("insertinto");
                mask.Add(" select * from INSERTED");
                mask.Add("ELSE raiserror('Foreign key violation',18,1)");

                MaskParser mp = new MaskParser(mask);
                if (mp.fitsMask((String)dr.ItemArray[1]))
                {
                    List<String> result = mp.parse((String)dr.ItemArray[1]);
                    //System.Console.WriteLine(result[4]);//pk for table 1[5]
                    //System.Console.WriteLine(result[5]);//table 1
                    //System.Console.WriteLine(result[7]);//pk for table 2[8]
                    //System.Console.WriteLine(result[8]);//table 2
                    //System.Console.WriteLine(result[13]);//FK table

                    Scheme t1 = s.getScheme(result[5]);
                    Scheme t2 = s.getScheme(result[8]);
                    Scheme ft = s.getScheme(result[13]);
                    ft.parents.Add(t1);
                    ft.parents.Add(t2);
                    t1.children.Add(ft.tableName);
                    t2.children.Add(ft.tableName);
                }
                else
                    System.Console.WriteLine("Unrecognized Trigger: " + (String)dr.ItemArray[1]);

            }
            */
            foreach (DataRow dr in primaryKeysResult.Rows)
            {
                if (((String)dr.ItemArray[2]).StartsWith("MSys") || ((Boolean)dr.ItemArray[6]) == false)    // 6: primarykey
                    continue;
                Scheme sch = s.getScheme((String)dr.ItemArray[2]);
                if (sch == null)
                    throw new Exception("Error processing primary keys");
                if (!sch.pkColumns.Contains(sch.getColumn((String)dr.ItemArray[17])))       // 17: column name
                    sch.pkColumns.Add(sch.getColumn((String)dr.ItemArray[17]));
            }
            return s;
        }

        private static void Swap(Schema s, Scheme child, Scheme parent)
        {
            int childIndex = s.tables.IndexOf(child);
            int parentIndex = s.tables.IndexOf(parent);
            s.tables[childIndex] = parent;
            s.tables[parentIndex] = child;
        }

        public static Schema QuerySchemaFromDB(OracleConnection conn, String username)
        {

            DataTable columnsResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.Columns);//Columns - Gets Columns by table
            DataTable tablesResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.Views);//view name in index 2
            DataTable primaryKeysResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.IndexColumns);

            // filter Oracle system table (because there are too many)
            DataView dvColumnResult = new DataView(columnsResult, "OWNER = '" + username + "'", "TABLE_NAME", DataViewRowState.CurrentRows);
            DataView dvTableResult = new DataView(tablesResult, "OWNER = '" + username + "'", "VIEW_NAME", DataViewRowState.CurrentRows);
            DataView dvPrimaryKeyResult = new DataView(primaryKeysResult, "TABLE_OWNER = '" + username + "'", "TABLE_NAME", DataViewRowState.CurrentRows);

            List<String> views = new List<String>();
            foreach (DataRowView dr in dvTableResult)
                views.Add(dr[1].ToString());

            String q = "SELECT \r\n" +
                    "C.TABLE_NAME AS FK_TABLE, \r\n" +
                    "A.COLUMN_NAME as FK_COLUMN, \r\n" +
                    "B.TABLE_NAME AS PK_TABLE,  \r\n" +
                    "B.COLUMN_NAME AS PK_COLUMN \r\n" +
                    "FROM ALL_CONS_COLUMNS A JOIN ALL_CONSTRAINTS C  ON A.CONSTRAINT_NAME = C.CONSTRAINT_NAME \r\n" +
                    "JOIN ALL_CONS_COLUMNS B ON B.CONSTRAINT_NAME = C.R_CONSTRAINT_NAME \r\n" +
                    "WHERE C.CONSTRAINT_TYPE = 'R' AND C.OWNER = '" + username.ToUpper() + "'";
            OracleDataAdapter sda = new OracleDataAdapter(q, conn);
            DataSet ds = new DataSet();
            sda.Fill(ds);
            DataTable foreignKeysResult = ds.Tables[0];

            //sda = new OracleDataAdapter("SELECT name, text FROM sys.triggers JOIN syscomments ON sys.triggers.object_id = syscomments.id", conn);
            //ds = new DataSet();
            //sda.Fill(ds);
            //DataTable triggersResult = ds.Tables[0];


            Schema s = new Schema();


            foreach (DataRowView dr in dvColumnResult)
            {
                if (!views.Contains((String)dr[2]))
                {
                    Scheme current = s.getScheme((String)dr[1]);
                    if (current == null)
                        current = s.newScheme((String)dr[1], null);
                    GuavaData.Domain.Domain d = null;
                    if ((String)dr[4] == "uniqueidentifier")
                        d = new Domain.UniqueIdentifier();
                    else if ((String)dr[4] == "DATE")
                        d = new Domain.DateTime();
                    else if ((String)dr[4] == "text")
                        d = new Domain.String();
                    else if ((String)dr[4] == "tinyint")
                        d = new Domain.TinyInteger();
                    else if ((String)dr[4] == "smallint")
                        d = new Domain.SmallInteger();
                    else if ((String)dr[4] == "NUMBER")
                        d = new Domain.Integer();
                    else if ((String)dr[4] == "bit")
                        d = new Domain.Boolean();
                    else if (((String)dr[4]).Contains("VARCHAR2"))
                        d = new Domain.String();//normally we'd specify a length, but I'm not sure which ItemArray index specifies this and it doesnt really matter to us.
                    else if ((String)dr[4] == "char")
                        d = new Domain.TinyInteger();
                    else if ((String)dr[4] == "bigint")
                        d = new Domain.BigInteger();
                    else if ((String)dr[4] == "numeric")
                        d = new Domain.Integer();
                    else if ((String)dr[4] == "FLOAT")
                        d = new Domain.Single();
                    else if ((String)dr[4] == "LONG")
                        d = new Domain.BigInteger();
                    else if ((String)dr[4] == "image")
                        d = new Domain.String();
                    else if ((String)dr[4] == "smalldatetime")
                        d = new Domain.DateTime();
                    else if ((String)dr[4] == "nchar")
                        d = new Domain.String();
                    else if ((String)dr[4] == "timestamp" || (String)dr[4] == "date")
                        d = new Domain.DateTime();
                    else
                        throw new Exception("Data type " + (String)dr[4] + " not recognized.");
                    current.newColumn((String)dr[2], d, false, false);
                }

            }

            foreach (DataRow dr in foreignKeysResult.Rows)
            {
                Scheme child = s.getScheme(((String)dr.ItemArray[0]));
                Scheme parent = s.getScheme(((String)dr.ItemArray[2]));
                if (child == null || parent == null)
                    throw new Exception("Error processing foreign keys");
                child.parents.Add(parent);
                parent.children.Add(child.tableName);
                Column newCol = child.getColumn((String)dr.ItemArray[1]);
                if (!child.fkColumns.Contains(newCol))
                    child.fkColumns.Add(newCol);
                newCol = parent.getColumn((String)dr.ItemArray[3]);
                if (!parent.pkColumns.Contains(newCol))
                    parent.pkColumns.Add(newCol);


            }

            /*
            foreach (DataRow dr in triggersResult.Rows)
            {
                List<String> mask = new List<String>();
                mask.Add("CREATE Trigger ");
                mask.Add("ON");
                mask.Add("INSTEAD OF INSERT AS DECLARE @count int SELECT @count=count(*) FROM INSERTED I@ WHERE NOT EXISTS (SELECT UNIONED@SELECTS");
                mask.Add(" FROM (SELECT");
                mask.Add("as");
                mask.Add("FROM");
                mask.Add("UNION SELECT");
                mask.Add("as");
                mask.Add("FROM");
                mask.Add(") AS UNIONED@SELECTS WHERE I@.");
                mask.Add("=UNIONED@SELECTS.");
                mask.Add(")");
                mask.Add("IF(@count=0)");
                mask.Add("insertinto");
                mask.Add(" select * from INSERTED");
                mask.Add("ELSE raiserror('Foreign key violation',18,1)");

                MaskParser mp = new MaskParser(mask);
                if (mp.fitsMask((String)dr.ItemArray[1]))
                {
                    List<String> result = mp.parse((String)dr.ItemArray[1]);
                    //System.Console.WriteLine(result[4]);//pk for table 1[5]
                    //System.Console.WriteLine(result[5]);//table 1
                    //System.Console.WriteLine(result[7]);//pk for table 2[8]
                    //System.Console.WriteLine(result[8]);//table 2
                    //System.Console.WriteLine(result[13]);//FK table

                    Scheme t1 = s.getScheme(result[5]);
                    Scheme t2 = s.getScheme(result[8]);
                    Scheme ft = s.getScheme(result[13]);
                    ft.parents.Add(t1);
                    ft.parents.Add(t2);
                    t1.children.Add(ft.tableName);
                    t2.children.Add(ft.tableName);
                }
                else
                    System.Console.WriteLine("Unrecognized Trigger: " + (String)dr.ItemArray[1]);

            }
             */

            foreach (DataRowView dr in dvPrimaryKeyResult)
            {
                Scheme sch = s.getScheme((String)dr[3]);
                if (sch != null)
                    if (!sch.pkColumns.Contains(sch.getColumn((String)dr[4])))
                        sch.pkColumns.Add(sch.getColumn((String)dr[4]));
            }
            return s;
        }

        public static Schema QuerySchemaFromDB(Npgsql.NpgsqlConnection conn)
        {
            DataTable columnsResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.Columns);//Columns - Gets Columns by table
            DataTable tablesResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.Views);//view name in index 2
            DataTable primaryKeysResult = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.IndexColumns);

            // filter Oracle system table (because there are too many)
            DataView dvColumnResult = new DataView(columnsResult, "table_schema = 'public'", "table_name", DataViewRowState.CurrentRows);
            DataView dvTableResult = new DataView(tablesResult, "table_schema = 'public'", "table_name", DataViewRowState.CurrentRows);
            DataView dvPrimaryKeyResult = new DataView(primaryKeysResult, "table_schema = 'public'", "table_name", DataViewRowState.CurrentRows);

            List<String> views = new List<String>();
            foreach (DataRowView dr in dvTableResult)
                views.Add(dr[2].ToString());

            //Query adapted from: http://blog.sqlauthority.com/2006/11/01/sql-server-query-to-display-foreign-key-relationships-and-name-of-the-constraint-for-each-table-in-database/
            String q = "SELECT" + "\r\n" +
                "FK.TABLE_NAME as FK_Table," + "\r\n" +
                "CU.COLUMN_NAME as FK_Column," + "\r\n" +
                "PK.TABLE_NAME as PK_Table," + "\r\n" +
                "PT.COLUMN_NAME as PK_Column" + "\r\n" +
                "FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME" + "\r\n" +
                "INNER JOIN (" + "\r\n" +
                "SELECT i1.TABLE_NAME, i2.COLUMN_NAME" + "\r\n" +
                "FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1" + "\r\n" +
                "INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME" + "\r\n" +
                "WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY'" + "\r\n" +
                ") PT ON PT.TABLE_NAME = PK.TABLE_NAME";
            Npgsql.NpgsqlDataAdapter sda = new Npgsql.NpgsqlDataAdapter(q, conn);
            DataSet ds = new DataSet();
            sda.Fill(ds);
            DataTable foreignKeysResult = ds.Tables[0];

            //sda = new MySqlDataAdapter("SELECT name, text FROM sys.triggers JOIN syscomments ON sys.triggers.object_id = syscomments.id", conn);
            //ds = new DataSet();
            //sda.Fill(ds);
            //DataTable triggersResult = ds.Tables[0];


            Schema s = new Schema();

            foreach (DataRowView dr in dvColumnResult)
            {
                if (!views.Contains((String)dr[2]))
                {
                    Scheme current = s.getScheme((String)dr[2]);
                    if (current == null)
                        current = s.newScheme((String)dr[2], null);
                    GuavaData.Domain.Domain d = null;
                    if ((String)dr[7] == "date")
                        d = new Domain.DateTime();
                    else if ((String)dr[7] == "text")
                        d = new Domain.String();
                    else if ((String)dr[7] == "int2")
                        d = new Domain.SmallInteger();
                    else if ((String)dr[7] == "int")
                        d = new Domain.Integer();
                    else if ((String)dr[7] == "bit")
                        d = new Domain.Boolean();
                    else if ((String)dr[7] == "bool")
                        d = new Domain.Boolean();
                    else if ((String)dr[7] == "bpchar")
                        d = new Domain.String();
                    else if ((String)dr[7] == "numeric")
                        d = new Domain.Integer();
                    else if ((String)dr[7] == "float4")
                        d = new Domain.Single();
                    else if ((String)dr[7] == "decimal")
                        d = new Domain.Decimal();
                    else
                        d = new Domain.String();
                    current.newColumn((String)dr[3], d, false, false);
                }

            }

            foreach (DataRow dr in foreignKeysResult.Rows)
            {
                Scheme child = s.getScheme(((String)dr.ItemArray[0]));
                Scheme parent = s.getScheme(((String)dr.ItemArray[2]));
                if (child == null || parent == null)
                    throw new Exception("Error processing foreign keys");
                child.parents.Add(parent);
                parent.children.Add(child.tableName);
                Column newCol = child.getColumn((String)dr.ItemArray[1]);
                if (!child.fkColumns.Contains(newCol))
                    child.fkColumns.Add(newCol);
                newCol = parent.getColumn((String)dr.ItemArray[3]);
                if (!parent.pkColumns.Contains(newCol))
                    parent.pkColumns.Add(newCol);


            }
            /*
            foreach (DataRow dr in triggersResult.Rows)
            {
                List<String> mask = new List<String>();
                mask.Add("CREATE Trigger ");
                mask.Add("ON");
                mask.Add("INSTEAD OF INSERT AS DECLARE @count int SELECT @count=count(*) FROM INSERTED I@ WHERE NOT EXISTS (SELECT UNIONED@SELECTS");
                mask.Add(" FROM (SELECT");
                mask.Add("as");
                mask.Add("FROM");
                mask.Add("UNION SELECT");
                mask.Add("as");
                mask.Add("FROM");
                mask.Add(") AS UNIONED@SELECTS WHERE I@.");
                mask.Add("=UNIONED@SELECTS.");
                mask.Add(")");
                mask.Add("IF(@count=0)");
                mask.Add("insertinto");
                mask.Add(" select * from INSERTED");
                mask.Add("ELSE raiserror('Foreign key violation',18,1)");

                MaskParser mp = new MaskParser(mask);
                if (mp.fitsMask((String)dr.ItemArray[1]))
                {
                    List<String> result = mp.parse((String)dr.ItemArray[1]);
                    //System.Console.WriteLine(result[4]);//pk for table 1[5]
                    //System.Console.WriteLine(result[5]);//table 1
                    //System.Console.WriteLine(result[7]);//pk for table 2[8]
                    //System.Console.WriteLine(result[8]);//table 2
                    //System.Console.WriteLine(result[13]);//FK table

                    Scheme t1 = s.getScheme(result[5]);
                    Scheme t2 = s.getScheme(result[8]);
                    Scheme ft = s.getScheme(result[13]);
                    ft.parents.Add(t1);
                    ft.parents.Add(t2);
                    t1.children.Add(ft.tableName);
                    t2.children.Add(ft.tableName);
                }
                else
                    System.Console.WriteLine("Unrecognized Trigger: " + (String)dr.ItemArray[1]);

            }
            */
            foreach (DataRowView dr in dvPrimaryKeyResult)
            {
                Scheme sch = s.getScheme((String)dr[2]);
                if (sch == null)
                    throw new Exception("Error processing primary keys");
                if (!sch.pkColumns.Contains(sch.getColumn((String)dr[4])))
                    sch.pkColumns.Add(sch.getColumn((String)dr[4]));
            }
            return s;
        }

        public static String textBetween(String s, String a, String b, int startIndex)
        {
            //a little helper function that returns the text between strings a + b in string s starting at position startIndex
            int x = s.IndexOf(a, startIndex);
            if (x == -1)
                return "";
            int y = s.IndexOf(b, x);
            if (y == -1)
                return "";
            String rv = s.Substring(x + a.Length, y - (x + a.Length));
            rv = rv.Trim();
            return rv;
        }

        public List<TreeNode> SchemaToTreeView(bool listView)
        {
            //Return the root nodes of a treeView tree that represents this schema. Making a tree occasionally produces
            //messy output (lots of duplicate nodes), so a plain list mode is available by calling SchemaToTreeView with
            //argument "list" equal to true.

            if (listView)
            {
                List<TreeNode> rv = new List<TreeNode>();
                foreach (Scheme s in tables)
                    rv.Add(new TreeNode(s.tableName));
                return rv;
            }

            // Copy the curent list of Schemes so we can modify it
            List<Scheme> toAdd = new List<Scheme>();
            List<String> added = new List<String>();
            foreach (Scheme s in tables)
                toAdd.Add(s);

            List<TreeNode> forest = new List<TreeNode>();
            TreeNode tn = new TreeNode("Empty Schema");

            //if there are no tables in this schema, return an empty tree
            if (toAdd.Count == 0)
            {
                forest.Add(tn);
                return forest;
            }

            List<int> currentPosition = new List<int>();
            //get the starting table
            while (toAdd.Count > 0)
            {
                Scheme scheme = null;
                scheme = toAdd[0];

                // try to start from a table at top of hierarchy

                while (scheme.parents.Count > 0)
                    scheme = scheme.parents[0];

                tn = new TreeNode(scheme.TableName);
                toAdd.Remove(scheme);
                currentPosition.Add(scheme.GetHashCode());
                SchemaToTreeViewHelper(toAdd, tn, scheme, currentPosition);
                currentPosition.Remove(scheme.GetHashCode());
                forest.Add(tn);
            }

            return forest;
        }

        private void SchemaToTreeViewHelper(List<Scheme> toAdd, TreeNode parentNode, Scheme parentScheme, List<int> currentPosition)
        {
            //Recursively processes a schema in order to display tables with foreign keys to a given table as children of that table

            foreach (String s in parentScheme.children)
            {
                Scheme cs = getScheme(s);
                if (!currentPosition.Contains(cs.GetHashCode()))//keeps us from getting stuck in a loop
                {
                    TreeNode ctn = new TreeNode(cs.TableName);
                    bool add = true;
                    foreach (TreeNode temp in parentNode.Nodes)
                        if (temp.Text == ctn.Text)
                            add = false;
                    if (add)
                        parentNode.Nodes.Add(ctn);
                    if (toAdd.Contains(cs))
                        toAdd.Remove(cs);
                    currentPosition.Add(cs.GetHashCode());
                    SchemaToTreeViewHelper(toAdd, ctn, cs, currentPosition);
                    currentPosition.Remove(cs.GetHashCode());
                }

            }
        }

        public static Scheme outerUnionScheme(string resultName, List<Scheme> ls)
        {
            // Loop through the tables, using the first item on the list as the primary key template.  Ignore foreign keys unless they are consistent.
            if (ls.Count <= 0) throw new OuterUnionException("Cannot make a union of an empty set of tables.");
            Scheme sResult = ls[0].container.newScheme(resultName, null);
            bool bFirst = true;
            foreach (Scheme sTemp in ls)
            {
                if (bFirst)
                {
                    // Copy the first table over fully
                    foreach (Column cTemp in sTemp.columns)
                        sResult.newColumn(cTemp.Name, cTemp.dom, sTemp.pkColumns.Contains(cTemp), sTemp.fkColumns.Contains(cTemp));

                    bFirst = false;
                    continue;
                }

                // For each column in the table:
                // - If it is a primary key column, make sure that it is in the result already
                // - If it is not a primary key column, make sure that it is not a foreign key in the result
                // Also, make sure that all primary key columns in the result exist in the table
                foreach (Column cTemp in sTemp.columns)
                {
                    if (sTemp.pkColumns.Contains(cTemp))
                    {
                        // Does the key column already exist?  If not, return null
                        bool bFound = false;
                        foreach (Column cResultTemp in sResult.pkColumns) if (cTemp.Equals(cResultTemp))
                            {
                                bFound = true;
                                break;
                            }
                        if (bFound) continue;

                        // Key column not found, so return null
                        throw new OuterUnionException("Primary key columns of input tables do not match.");
                    }
                    else
                    {
                        // Non-key column.  Make sure that same column doesn't exist with same name but different domain.
                        bool bFound = false;
                        foreach (Column cResultTemp in sResult.columns)
                        {
                            if (cTemp.Equals(cResultTemp))
                            {
                                // Found the column.  If result is fk but the other isn't, remove from the fk list of the result
                                if (sResult.fkColumns.Contains(cResultTemp) && !(sTemp.fkColumns.Contains(cTemp))) sResult.fkColumns.Remove(cResultTemp);
                                if (sResult.pkColumns.Contains(cResultTemp)) throw new OuterUnionException("Primary key columns of input tables do not match.");
                                bFound = true;
                                break;
                            }
                            else
                            {
                                // Make sure that the column doesn't have the same name but different domain
                                if (cTemp.Name.Equals(cResultTemp.Name) && !(cTemp.dom.Equals(cResultTemp.dom))) throw new OuterUnionException("Column " + cResultTemp.Name + " with domain " + cResultTemp.dom.ToString() + " is already present with domain " + cTemp.dom.ToString() + ".");
                            }
                        }
                        if (bFound) break;

                        // If we get here, then the column needs to be added
                        sResult.newColumn(cTemp.Name, cTemp.dom, false, sTemp.fkColumns.Contains(cTemp));
                    }
                }

                // Finally, check to make sure that the primary key of the result does not strictly subsume the PK of the current table
                foreach (Column cResultTemp in sResult.pkColumns)
                {
                    // Does the key column exist?  If not, return null
                    bool bFound = false;
                    foreach (Column cTemp in sTemp.pkColumns) if (cTemp.Equals(cResultTemp))
                        {
                            bFound = true;
                            break;
                        }
                    if (bFound) break;

                    // Key column not found, so return error
                    throw new OuterUnionException("Primary key columns of input tables do not match.");
                }
            }
            return sResult;
        }

        public static Scheme outerUnionSchemeWithProvenance(string resultName, List<Scheme> ls, string provColumnName)
        {
            Scheme s = Schema.outerUnionScheme(resultName, ls);

            // Create a new column that is the provenance column
            List<string> lString = new List<string>();
            foreach (Scheme sTemp in ls) lString.Add(sTemp.TableName);
            GuavaData.Domain.Enumerated e = new GuavaData.Domain.Enumerated(lString.ToArray());
            s.newColumn(provColumnName, e, true, false);

            return s;
        }

        public Scheme newScheme(string name, Scheme parent)
        {
            Scheme temp = new Scheme(name, parent);
            this.tables.Add(temp);
            if (parent != null)
            {
                parent.children.Add(name);
            }
            temp.container = this;
            return temp;
        }

        public override string ToString()
        {
            string temp = "";

            foreach (Scheme s in tables)
            {
                temp = temp + s.ToString() + Environment.NewLine;
            }

            return temp;
        }

        public void CreateDB(System.Data.SqlClient.SqlConnection conn)
        {
            conn.Open();

            foreach (Scheme s in this.tables)
            {
                System.Data.SqlClient.SqlCommand c = new System.Data.SqlClient.SqlCommand(s.ToString(), conn);
                c.ExecuteNonQuery();
            }

            conn.Close();
        }

        public Scheme this[string s]
        {
            get
            {
                foreach (Scheme sch in this.tables)
                    if (sch.TableName == s)
                        return sch;
                return null;
            }
        }

        public Transaction Transaction
        {
            get
            {
                Transaction t = new Transaction();
                //for (int i = tables.Count - 1 ; i >= 0; i--)
                foreach (Scheme s in tables)
                {
                    s.AppendTransaction(t);
                }
                return t;
            }
        }

        public override int GetHashCode()
        {
            return this.Transaction.ToString().GetHashCode();
        }
    }

    public class Scheme : Object
    {
        public string tableName;
        public Schema container;
        public List<Column> columns;
        public List<Scheme> parents;
        public List<Column> pkColumns;
        public List<Column> fkColumns;
        public List<String> children;

        public Scheme(string name, Scheme parent)
        {
            this.tableName = name;
            parents = new List<Scheme>();
            if (parent != null && !parents.Contains(parent))
                this.parents.Add(parent);
            columns = new List<Column>();
            pkColumns = new List<Column>();
            fkColumns = new List<Column>();
            children = new List<string>();
        }

        public TreeNode getDetailsTreeNode()
        {
            TreeNode rootNode = new TreeNode(TableName);
            TreeNode pkNode = new TreeNode("Primary Key");
            String pkn = "";
            foreach (Column c in this.pkColumns)
                pkn += c.Name + ",";
            if (pkn.Length > 0)
                pkn = pkn.Substring(0, pkn.Length - 1);
            else
                pkn = "No Primary Key Specified";
            pkNode.Nodes.Add(pkn);
            pkNode.ForeColor = System.Drawing.Color.Green;
            rootNode.Nodes.Add(pkNode);

            TreeNode fkNode = new TreeNode("Foreign Keys");
            fkNode.ForeColor = System.Drawing.Color.Green;
            foreach (Column c in this.fkColumns)
                fkNode.Nodes.Add(c.Name);
            fkNode.Collapse();
            rootNode.Nodes.Add(fkNode);

            TreeNode columnNode = new TreeNode("Columns");

            foreach (Column c in columns)
            {
                TreeNode n = new TreeNode(c.Name);
                n.ForeColor = System.Drawing.Color.Blue;
                columnNode.Nodes.Add(n);
                if (c.dom != null)
                {
                    TreeNode n2 = new TreeNode(c.dom.ToString());
                    n2.ForeColor = System.Drawing.Color.Purple;
                    if (c.dom.ToString() == "Enumerated")
                        foreach (String s in ((Domain.Enumerated)c.dom).Items)
                            n2.Nodes.Add(new TreeNode(s));
                    n.Nodes.Add(n2);
                }
                /*
                if (c.isEnumnerated())
                    foreach (String s in c.enumeration)
                        n.Nodes.Add(s);
                 */
            }
            rootNode.Nodes.Add(columnNode);
            TreeNode p = new TreeNode("Parent Table");
            if (this.parents.Count > 0)
                foreach (Scheme s1 in parents)
                    p.Nodes.Add(new TreeNode(s1.TableName));
            rootNode.Nodes.Add(p);
            TreeNode ct = new TreeNode("Child Tables");
            foreach (String s in children)
                ct.Nodes.Add(new TreeNode(s));
            rootNode.Nodes.Add(ct);
            return rootNode;
        }

        public Column newColumn(string name, GuavaData.Domain.Domain type, bool pk, bool fk)
        {
            Column temp = new Column(name, type);
            if (!this.columns.Contains(temp))
                this.columns.Add(temp);
            if (pk && !pkColumns.Contains(temp)) this.pkColumns.Add(temp);
            if (fk && !fkColumns.Contains(temp)) this.fkColumns.Add(temp);
            return temp;
        }

        public Column getColumn(String name)
        {
            foreach (Column c in columns)
                if (c.Name == name)
                    return c;
            throw new Exception("Column " + name + " not found.");
        }

        public string TableName
        {
            get { return tableName; }
            set { tableName = value; }
        }

        public string pkString
        {
            get
            {
                string build = "";

                if (pkColumns.Count <= 0) return build;

                bool first = true;
                foreach (Column column in pkColumns)
                {
                    if (first) build += column.Name;
                    else build += ", " + column.Name;
                    first = false;
                }

                return build;
            }
        }

        public string fkString
        {
            get
            {
                string build = "";

                if (fkColumns.Count <= 0) return build;

                bool first = true;
                foreach (Column column in fkColumns)
                {
                    if (first) build += column.Name;
                    else build += ", " + column.Name;
                    first = false;
                }

                return build;
            }
        }

        public override string ToString()
        {
            if (columns.Count <= 0) throw new Exception("Cannot create table " + tableName + " with no columns.");
            if (pkColumns.Count <= 0) throw new Exception("Cannot create table " + tableName + " with no primary key.");
            string build = "CREATE TABLE " + this.tableName + " (";

            // Define columns with their associated data types
            foreach (Column column in columns) build += column.ToString() + ", ";

            // Add primary key
            if (this.pkString != "")
                build += "CONSTRAINT pk_" + this.tableName + " PRIMARY KEY CLUSTERED (" + this.pkString + ")";

            // Add foreign key, if only one parent
            Scheme sParent = null;
            foreach (Scheme sTemp in this.container.tables)
            {
                if (sTemp.children.Contains(this.tableName))
                {
                    if (sParent == null) sParent = sTemp;
                    else return build + ")";
                }
            }

            if (sParent != null)
            {
                // We also have a foreign key to create
                build += ", CONSTRAINT fk_" + this.tableName + " FOREIGN KEY (" + this.fkString + ") REFERENCES ";
                build += sParent.tableName + " (" + sParent.pkString + ") ON DELETE CASCADE ON UPDATE CASCADE";
            }

            build += ")";

            return build;
        }

        public Column this[string s]
        {
            get
            {
                foreach (Column c in columns) if (c.Name == s) return c;
                return null;
            }
        }

        internal void AppendTransaction(Transaction t)
        {
            // Create the add table statement
            AddTable at = new AddTable(this.tableName);
            foreach (Column c in columns)
                at.AddColumn(c.Name, c.dom, pkColumns.Contains(c));

            // Add the create table statement, if the table does not exist yet
            bool found = false;
            foreach (var c in t.Contents)
                if (c.root is AddTable)
                    if ((c.root as AddTable).Table == this.tableName)
                        found = true;
            if (!found)
                t.Add(new Command(at));

            // Create the foreign key statement(s), if necessary
            if (fkColumns.Count > 0 && parents.Count > 0)
            {
                foreach (Scheme parentScheme in parents)
                {
                    // Dummy check - number of fk columns matches the number of pk columns in parent (required in view model, not within channel)
                    if (fkColumns.Count != parentScheme.pkColumns.Count)
                        ;// throw new Exception("Number of foreign key columns does not match number of primary key columns in the parent table.");

                    ForeignKey fk = new ForeignKey(this.tableName, parentScheme.TableName);

                    if (parentScheme.pkColumns.Count < fkColumns.Count)
                    {
                        for (int i = 0; i < parentScheme.pkColumns.Count; i++)
                            fk.AddColumn(fkColumns[i].Name, parentScheme.pkColumns[i].Name);
                    }
                    else
                    {
                        for (int i = 0; i < fkColumns.Count; i++)
                            fk.AddColumn(fkColumns[i].Name, parentScheme.pkColumns[i].Name);
                    }
                    /*
                    List<String> childColumns = new List<String>();
                    foreach (Column c in this.fkColumns)
                        childColumns.Add(c.Name);
                    List<String> parentColumns = new List<String>();
                    foreach(Column c in parentScheme.pkColumns)
                        parentColumns.Add(c.Name);
                    ForeignKey fk = new ForeignKey(this.tableName, new List<String>(), new List<Condition>(), childColumns, 
                        parentScheme.tableName, new List<String>(), new List<Condition>(), parentColumns);
                     * */

                    //check if this foreign key already exists in the transaction so we don't
                    //create redundant tier2 foreign keys
                    found = false;
                    foreach (var c in t.Contents)
                    {
                        if (c.root is ForeignKey)
                        {
                            ForeignKey checkKey = (ForeignKey)c.root;
                            if (checkKey.ChildTable.Equals(this.tableName) && checkKey.ParentTable.Equals(parentScheme.tableName))
                                found = true;
                        }
                    }

                    // Add foreign key statement to transaction
                    if (!found)
                        t.Add(new Command(fk));
                }
            }
        }
    }

    public class Column : Object
    {
        string columnName;
        public GuavaData.Domain.Domain dom;

        public Column(string name, GuavaData.Domain.Domain type)
        {
            this.columnName = name;
            this.dom = type;
        }

        public override string ToString()
        {
            return columnName + " " + dom.DBType;
        }

        public string Name
        {
            get { return columnName; }
            set { columnName = value; }
        }

        public Column Clone()
        {
            return new Column(columnName, dom);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Column)) return false;
            Column c = obj as Column;
            return (this.columnName.Equals(c.columnName) && this.dom.Equals(c.dom));
        }

        public override int GetHashCode()
        {
            return columnName.GetHashCode() + dom.GetHashCode();
        }
    }

    public enum DMLType { INSERT, UPDATE, DELETE };

    public class Common : Object
    {
        // Outputs the string representation of the object, and wraps it in a delimiter if necessary
        public static string Wrap(object o)
        {
            if (o is ColumnReference) return (o as ColumnReference).columnAlias;
            if (o is string) return "'" + o.ToString() + "'";
            if (o is Guid) return "'" + o.ToString() + "'";
            if (o is DateTime) return "'" + o.ToString() + "'";
            if (o is DBNull) return "NULL";
            if (o is bool)
            {
                return o.Equals(true) ? "1" : "0";
            }
            if (o == null) return "NULL";

            return "" + o.ToString() + "";
        }
    }
}
